昨日我們已經介紹 Kotlin 類別的基本使用方式,接下來我們來談繼承
、介面
與抽象
的使用方法,在 Kotlin 中,我們要使用繼承
時,會有以下三件事要注意:
:
操作符號class
前面加上 open
關鍵字我們利用上面三點事項撰寫下面範例:
fun main() {
// 呼叫 Author 類別
val author = Author("Devin")
// 印出 Devin
println(author.name)
}
// 加上 open 關鍵字代表此類別可被繼承
open class Person(name: String){
val name: String = name
}
// 建立一個 Author 類別繼承 Person 類別
class Author(name: String) : Person(name)
而當子類別
繼承後,如果子類別要使用父類別的函數,我們就要使用到 super
關鍵字進行呼叫,如下範例:
fun main() {
val man1 = SuperMan("Devin")
val man2 = SuperMan("Eric", "Eric@Eric.com")
println(man1.name) // 印出 Devin
println(man1.email) // 印出 ""
println(man2.name) // 印出 Eric
println(man2.email) // 印出 "Eric@Eric.com"
}
open class Person(val name: String) {
var email: String = ""
constructor(name: String, email: String) : this(name) {
this.email = email
}
}
class SuperMan : Person {
constructor(name: String) : super(name)
constructor(name: String, email: String) : super(name, email)
}
當子類別繼承父類別後,若子類別想要覆寫函數可使用 override
關鍵字,記得繼承的函數也要使用 open
關鍵字進行宣告,如下範例:
fun main() {
val man = SuperMan("Devin")
man.sayHello()
}
open class Person(val name: String) {
var email: String = ""
constructor(name: String, email: String) : this(name) {
this.email = email
}
open fun sayHello() {
println("Hi, 我是Person")
}
}
class SuperMan : Person {
constructor(name: String) : super(name)
constructor(name: String, email: String) : super(name, email)
// 覆寫方法
override fun sayHello() {
println("Hi, 我是SuperMan")
}
}
在 Kotlin 中使用繼承時,要注意程式執行先後順序
,我們會直接利用範例搭配下面步驟逐步觀察:
fun main() {
val man = SuperMan("Devin")
man.sayHello()
}
open class Person(open val name: String) {
var email: String = ""
// 執行步驟 2
init {
println("Person init 區塊")
}
// 執行步驟 3
constructor(name: String, email: String) : this(name) {
println("Person Name: $name")
this.email = email
}
// 執行步驟 5
open fun sayHello() {
println("Hi, 我是Person")
}
}
// 執行步驟 1
class SuperMan(override val name: String) : Person(name, email = "Test".also { println("帶入 Email 資料") }) {
// 執行步驟 4
init {
println("SuperMan init 區塊")
}
// 執行步驟 6
override fun sayHello() {
super.sayHello()
println("Hi, 我是SuperMan")
}
}
在繼承特性中,我們可以使用 var
定義的變數覆寫(override) val
父類別屬性,但要記得我們無法使用 val
覆寫(override) var
屬性,如下範例:
fun main() {
SuperMan("devin")
}
// 定義可繼承的 Person 類別與 val name 屬性
open class Person(open val name: String)
// override 父類別屬性,將 val 改為 var
class SuperMan(private var _name: String): Person(_name) {
override var name: String = _name
get() = field.capitalize()
set(value) {
field = value.trim()
}
init {
println(name)
}
}
Kotlin 與 Java 一樣,只能繼承一個類別,但可以實作多個介面,而介面實作也是使用 :
操作符號進行實現,而 Kotlin 與 Java 不同的地方是 Kotlin 的 Interface 可以自己實作函數,而使用介面的好處主要是為了解決耦合問題(Coupling)與支援多重繼承功能,例如以下範例:
fun main() {
val myClass = MyClass()
myClass.sayHello()
myClass.printData()
}
interface Interface1 {
fun printData()
// 介面本身自已實作
fun haveImplement() {
println("Kotlin 介面可自己實作,而且類別不需要實作")
}
}
interface Interface2 {
fun sayHello()
}
// 繼承多重介面
class MyClass : Interface1, Interface2 {
override fun printData() {
// 呼叫介面已實作函數
haveImplement()
}
override fun sayHello() {
println("Hi")
}
}
當介面方法相同時,我們可以使用 super 關鍵字進行呼叫特定介面的方法,如下範例:
fun main() {
MyClass().haveImplement()
}
interface Interface1 {
fun haveImplement() {
println("Interface1 實作")
}
}
interface Interface2 {
fun haveImplement() {
println("Interface2 實作")
}
}
// 繼承多重介面
class MyClass : Interface1, Interface2 {
override fun haveImplement() {
// 利用 super 呼叫指定介面的函數
super<Interface2>.haveImplement()
}
}
前面提到的耦合(Coupling)其實就是指兩個模組之間的相依性,若相依性越高,則耦合度越高,即為高耦合問題,耦合性越高的話,容易因為小需求變動而連貫影響整個系統或其他模組,例如以下範例,類別 A 與 類別 B 存在直接相依性的問題:
class A {
val message: String
}
class B {
fun sayHello(a: A) {
println(a.message)
}
}
實現低耦合就是對兩類別之間進行解耦,解除類別之間的直接關係,將直接關係轉換成間接關係:
將類別共用方法抽離成 Interface,再直接使用 override 方法執行
fun main() {
Boy().sayHello()
Girl().sayHello()
Woman().sayHello()
}
interface Person {
fun sayHello(): Unit
}
class Boy : Person {
override fun sayHello() {
println("Hello, Boy")
}
}
class Girl : Person {
override fun sayHello() {
println("Hello, Girl")
}
}
class Woman : Person {
override fun sayHello() {
println("Hello, Woman")
}
}
利用依賴注入(Dependency Injection, DI)方法達到類別彼此間的間接關係,即我們是將被依賴物件注入被動接收物件當中,以下面範例為例:
fun main() {
// 將被依賴物件注入被動接收物件
// 若學生有學習新的語言,只要新增一個類別再丟入 MyStudent 即可
MyStudent(English()).study()
MyStudent(Chinese()).study()
}
// 建立學生類別
class MyStudent(private val language: Language) {
// 正在讀書
fun study() {
language.speak()
}
}
// 建立邏輯共用介面-語言
interface Language {
fun speak();
}
// 當需要 English 時,建立類別
class English : Language {
override fun speak() {
println("學生正在練習英文口說")
}
}
// 當需要 Chinese 時,建立類別
class Chinese : Language {
override fun speak() {
println("學生正在練習中文口說")
}
}
在 Kotlin 中抽象類別會使用到 abstract 關鍵字,必須加在 class 或 function 前面,而抽象類別無法像普通類別一樣被實例化(Instance),它只能被類別繼承,而抽象類別也能使用建構函數進行外部參數引入,如下範例:
fun main() {
SuperMan("Devin", "").hello()
}
// 定義抽象類別
abstract class Person (val name: String, val email: String) {
abstract fun hello()
}
// 定義
class SuperMan(name: String, email: String) : Person(name, email) {
override fun hello() {
// 印出「我是 Devin」
println("我是 $name ")
}
}
抽象類別與介面主要差別還是在於使用場景或身份的不同,因類別只能單一繼承,所以使用抽象類別的子類別幾乎都是會有高關聯的,但介面不見得,我們可以依需求來選擇合適的介面實作,建議大家還是要依照需求來選擇合適方式。